﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using MyFloridaHouse.libSvOpenDistricts.Districts;
using MyFloridaHouse.libSvUtilities.Logging;
using MyFloridaHouse.ServiceConn.DistrBldrSvcRef;
using MyFloridaHouse.libSvOpenDistricts.DataService;
using MyFloridaHouse.libSvCommonUtilities;
using MyFloridaHouse.libSvUtilities;
using System.ComponentModel;
using MyFloridaHouse.libSvOpenDistricts.StorageIsolated;
using System.Windows;

namespace MyFloridaHouse.libSvOpenDistricts.Census
{
    public delegate void CensusGeometryInitializedEventHandler();

    public partial class CensusGeometry : INotifyPropertyChanged
    {

        #region events
        public static event CensusGeometryInitializedEventHandler CensusGeometryInitialized;

        private static void RaiseCensusGeometryInitialized(object sender)
        {
            if (CensusGeometryInitialized != null)
            {
                CensusGeometry._censusGeometryInstance.isInitializeComplete = true;
                CensusGeometryInitialized();
            }
            else
            {
                // todo: Log this error
                throw (new NotImplementedException("No Subscribers to CensusGeometryInitialized Event"));
            }
        }

        #endregion

        #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;
        protected void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
        #endregion


        private bool _isInitializeComplete = false;
        public bool isInitializeComplete
        {
            get { return _isInitializeComplete; }
            set { if (_isInitializeComplete != value) { _isInitializeComplete = value; NotifyPropertyChanged("isInitializeComplete"); } }
        }

        private bool _isInitializeError = false;
        public bool isInitializeError
        {
            get { return _isInitializeError; }
            set { if (_isInitializeError != value) { _isInitializeError = value; NotifyPropertyChanged("isInitializeError"); } }
        }

        #region   censusGeometryInstance - outside interface through static class
        private static CensusGeometry _censusGeometryInstance;
        public static CensusGeometry censusGeometryInstance
        {
            get { return _censusGeometryInstance; }
        }

        #endregion

        public List<CensusGeometryNode> nodeList { get { return censusGeometryNodeList; } }

        public List<CensusGeometryNode> censusGeometryNodeList {get; set; }

        public CensusGeometryHistory history { get; set; }

        public CensusGeometry(ServiceDAL serviceDAL)
        {
            // when the object is created, store a reference to it in the static class
            // That way anything that needs to access the instance methods can do so without passing around references all over the place
            _censusGeometryInstance = this;

            ServiceDAL.GetCensusGeometryCompleted += OnServiceDALGetCensusGeometryCompleted;
            serviceDAL.GetCensusGeometry();
        }


        private void OnServiceDALGetCensusGeometryCompleted(CensusGeometrySTO sto)
        {
            try
            {
                if (sto.packedDataList.Length > 0)
                {
                    byte[] serializedData = CompressionManager.DecompressToByteArray(sto.packedDataList);
                    CensusGeometryListDTO censusGeometryDeserializeList = (CensusGeometryListDTO)SerializationManager.Deserialize(typeof(CensusGeometryListDTO), serializedData);

                    // load up the data
                    CreateRootNode();
                    AddCensusGeometry(censusGeometryDeserializeList.censusGeometryList.ToList());

                    // save for quick load next time
                    IsolatedStorageHelper.SaveCensusGeometry(censusGeometryNodeList);
                }
                else
                {
                    try
                    {
                        // perform quick load
                        censusGeometryNodeList = new List<CensusGeometryNode>();
                        censusGeometryNodeList.AddRange(IsolatedStorageHelper.LoadCensusGeometry());
                        DetermineChildNodeLists();
                    }
                    catch (Exception ex)
                    {
                        // for any load error, delete the file - this will force the system to get the file from the cloud at next startup
                        IsolatedStorageHelper.DeleteCensusGeometry();
                    }
                }
            }
            catch (Exception ex)
            {
                isInitializeError = true;
                MessageBox.Show("There was an error loading the CensusGeometry.  Please refresh the page to restart");
            }
            CensusGeometry.RaiseCensusGeometryInitialized(this);
        }


        private void CreateRootNode()
        {
            censusGeometryNodeList = new List<CensusGeometryNode>();
            // create a root node in the plan (this represents the State)
            // it also shifts the items by one in the list, since uogid starts counting at 1
            CensusGeometryDTO rootNode = new CensusGeometryDTO();
            rootNode.uogID = 0;
            rootNode.uogType = 0;  // 0 indicates tree root //TODO: need to set up uogType as an enumeration
            rootNode.parentUogID = CensusGeometryConstants.noParentNode;
            CensusGeometryNode rootUog = new CensusGeometryNode(rootNode);
            censusGeometryNodeList.Add(rootUog);
        }

        private void AddCensusGeometry(List<CensusGeometryDTO> uogList)
        {
            // create a tree node for all items in the geometry list
            foreach (var uog in uogList)
            {
                censusGeometryNodeList.Add(new CensusGeometryNode(uog));
            }

            DetermineChildNodeLists();
        }

        private void DetermineChildNodeLists()
        {
            // initialize each
            foreach (var node in censusGeometryNodeList)
            {
                node.childNodeList = new List<CensusGeometryNode>();
            }

            // determine all the children of each node
            foreach (var node in censusGeometryNodeList)
            {
                if ((node.parentuogID >= 0) & (node.parentuogID < censusGeometryNodeList.Count()))
                {
                    censusGeometryNodeList[node.parentuogID].childNodeList.Add(node);
                }
            }
        }

        public void SetLockByDistrict(int districtID, bool newLockValue)
        {
            var districtNodesQuery = from dist in censusGeometryNodeList
                                     where dist.districtID == districtID
                                     select dist;

            SetNodesLock(districtNodesQuery.ToList(), newLockValue);
        }

        public void SetDistrictLockAll(bool newLockValue)
        {
            SetNodesLock(censusGeometryNodeList, newLockValue);
        }

        private void SetNodesLock(IEnumerable<CensusGeometryNode> nodeLockList, bool newLockValue)
        {
            foreach (var censusNode in nodeLockList)
            {
                if ((censusNode.districtID != CensusGeometryConstants.unassignedDistrictID)
                   & (censusNode.districtID != CensusGeometryConstants.multiDistrictID))
                {
                    censusNode.isLocked = newLockValue;
                }
            }
        }

        public void ResetDistrictAll()
        {
            for (int ndx = 0; ndx < censusGeometryNodeList.Count; ndx++)
            {
                censusGeometryNodeList[ndx].districtID = CensusGeometryConstants.unassignedDistrictID;
                censusGeometryNodeList[ndx].isLocked = false;
            }
            ResetCensusGeometryHistory();
        }

        private void ResetCensusGeometryHistory()
        {
            if (history == null)
            {
                history = new CensusGeometryHistory();
                // todo: Need to re-look at how history notifies UI that history is available
                //history.PropertyChanged += new PropertyChangedEventHandler(MemberPropertyChanged);
            }
            else
            {
                history.Reset();
            }
        }

        public int CountRegionsInDistrict(int targetDistrictID, List<UOG> districtReassignmentUogIDObservableCollection)
        {
            return (from x in districtReassignmentUogIDObservableCollection
                    where censusGeometryNodeList[x.uogID].districtID != targetDistrictID
                    select x.uogID).Count();
        }

        public void SetUogListToDistrict(int selectedDistrict, List<UOG> districtUogIDList)
        {
            List<int> intDistrictUogIDList = new List<int>();

            foreach (var shapeUogIDDTO in districtUogIDList)
            {
                intDistrictUogIDList.Add(shapeUogIDDTO.uogID);
            }
            SetUogListToDistrict(selectedDistrict, intDistrictUogIDList);
        }

        //todo: May be able to do a smart update in SetUogListToDistrict so it doesn't keep redoing parents for common children  (groupby parentID?)
        // maybe convert all to blocks and do a bypass update?  Lot's of promise in that idea
        public void SetUogListToDistrict(int selectedDistrict, List<int> districtUogIDList)
        {
            history.ChangeStarting();
            if (districtUogIDList.Count > CensusGeometryConstants.singleNodeLoadCountLimit)
            {
                BypassLoadWithUogIDList(selectedDistrict, districtUogIDList);
            }
            else
            {
                foreach (var uogID in districtUogIDList)
                {
                    SetUogToDistrict(selectedDistrict, uogID);
                }
            }
            history.ChangeComplete();
        }


        private void SetUogToDistrict(int districtID, int newUogID)
        {
            // limit the index so it won't allow a crash
            if ((newUogID < 0) | (newUogID >= censusGeometryNodeList.Count()))
            {
                Log.HandleInternalError("SetToDistrict attempted to use an invalid uogID [" + newUogID.ToString() + "]");
            }
            else
            {
                SetChildDistrict(districtID, newUogID);
                SetParentNodeToDistrict(districtID, newUogID);
            }
        }



        private UogDistrictStruct GetCensusNodeChildrenDistricts(int parentIndex)
        {
            UogDistrictStruct censusNodeChildren = new UogDistrictStruct();

            var distinctUogDistrictQUery = (from node in censusGeometryNodeList[parentIndex].childNodeList
                                            select node.districtID).Distinct();


            if (censusGeometryNodeList[parentIndex].childNodeList.Count() == 0)
            {
                censusNodeChildren.hasChildren = false;
                censusNodeChildren.allChildrenInSameDistrict = true;  // even if there are no children, they are all in the same district

                // these should never be used if hasChildren is false
                censusNodeChildren.allChildrenDistrictID = CensusGeometryConstants.multiDistrictID;
            }
            else if (distinctUogDistrictQUery.Count() == 1)
            {
                censusNodeChildren.hasChildren = true;
                censusNodeChildren.allChildrenInSameDistrict = true;
                censusNodeChildren.allChildrenDistrictID = censusGeometryNodeList[parentIndex].childNodeList[0].districtID;
            }
            else
            {
                censusNodeChildren.hasChildren = true;
                censusNodeChildren.allChildrenInSameDistrict = false;

                // this should never be used if allChildrenInSameDistrict is false
                censusNodeChildren.allChildrenDistrictID = CensusGeometryConstants.multiDistrictID;
            }

            return censusNodeChildren;
        }


        private void SetChildDistrict(int newDistrictID, int newUogID)
        {

            if (!censusGeometryNodeList[newUogID].isLocked)
            {
                // set all it's children to the current district
                foreach (var childNode in censusGeometryNodeList[newUogID].childNodeList)
                {
                    SetChildDistrict(newDistrictID, childNode.uogID);
                }
                SetNodeToDistrict(newDistrictID, newUogID);
            }
        }

    

        private void SetNodeToDistrict(int newDistrictID, int newUogID)
        {
            if (!censusGeometryNodeList[newUogID].isLocked)
            {
                int oldDistrictID = censusGeometryNodeList[newUogID].districtID;

                if (!GetCensusNodeChildrenDistricts(newUogID).allChildrenInSameDistrict)
                {
                    newDistrictID = CensusGeometryConstants.multiDistrictID;
                }

                if (oldDistrictID != newDistrictID)
                {
                    censusGeometryNodeList[newUogID].districtID = newDistrictID;
                    if (censusGeometryNodeList[newUogID].childNodeList.Count() == 0)
                    {
                        // only save history for the bottom level change, all else will propagate
                        history.SaveChange(oldDistrictID, newDistrictID, newUogID);
                    }
                }
            }

        }

        private void SetParentNodeToDistrict(int newDistrictID, int currentUogID)
        {
            // stop if we've reached root node
            if (censusGeometryNodeList[currentUogID].parentuogID >= 0)
            {
                int parentUogID = censusGeometryNodeList[currentUogID].parentuogID;
                if (newDistrictID != CensusGeometryConstants.multiDistrictID)
                {
                    if (!GetCensusNodeChildrenDistricts(parentUogID).allChildrenInSameDistrict)
                    {
                        // whoops, the current node has siblings in another district, its parent is multi-district
                        newDistrictID = CensusGeometryConstants.multiDistrictID;
                    }
                }
                SetNodeToDistrict(newDistrictID, parentUogID);
                SetParentNodeToDistrict(newDistrictID, parentUogID);
            }
        }

        public List<UOG> FindRegionsInUogIDList(List<UOG> uogIDList)
        {
            for (int ndx = 0; ndx < uogIDList.Count; ndx++)
            {
                // uogID is an index into the censusGeometryNodeList!
                uogIDList[ndx].regionID = censusGeometryNodeList[uogIDList[ndx].uogID].districtID;
            }
            return uogIDList;
        }

        public List<int> FindDistrictsInUogIDList(List<UOG> uogIDList)
        {
            List<int> districtList = new List<int>();

            foreach (var uog in uogIDList)
            {
                GetUogDistricts(ref districtList, uog.uogID);
            }

            var districtQuery = (from district in districtList
                                 select district).Distinct();
            return (districtQuery.ToList());

        }

        private void GetUogDistricts(ref List<int> districtList, int uogID)
        {
            // The uog sub-shapes could be spread across multiple districts
            // if so, need to find the district for each of child shape
            if (censusGeometryNodeList[uogID].districtID == CensusGeometryConstants.multiDistrictID)
            {
                foreach (var childNode in censusGeometryNodeList[uogID].childNodeList)
                {
                    GetUogDistricts(ref districtList, childNode.uogID);
                }
            }
            else
            {
                districtList.Add(censusGeometryNodeList[uogID].districtID);
            }
            return;
        }

        public int GetAssignedDistrict(int uogID)
        {
            if ((uogID < 1) & (uogID > censusGeometryNodeList.Count))
            {
                throw new Exception("Invalid UOGID [" + uogID.ToString() + "] detected in GetAssignedDistrict");
            }
            else
            {
                return censusGeometryNodeList[uogID].districtID;
            }
        }

        public string GetDistrictFromRegionID(int layerID, int regionID)
        {
            string district = "xxx";

            var districtQuery = from planNode in censusGeometryNodeList
                                where planNode.layerID == layerID
                                & planNode.regionID == regionID
                                select planNode.districtID.ToString();
            if (districtQuery.Count() == 1)
            {
                district = districtQuery.First();
                if (district == "0") district = "UnAssigned";
            }

            return district;
        }

        private List<int> GetUnassignedDistricts()
        {
            return (from planNode in censusGeometryNodeList
                    where planNode.districtID == 0
                    select planNode.regionID).ToList();
        }

    }
}
